package cn.chenli.job.tool;

import cc.tuhaolicai.mt.predefined.database.PageInfo;
import cc.tuhaolicai.mt.predefined.database.Paging;
import cc.tuhaolicai.mt.predefined.tool.SearchParam;
import cc.tuhaolicai.mt.predefined.tool.ToolPageInfo;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import org.asyou.mongo.Count;
import org.asyou.mongo.FindMany;
import org.asyou.mongo.Page;
import org.asyou.mongo.Update;
import org.asyou.mongo.base.MongoManager;
import org.asyou.mongo.dao.IMongoAdapter;
import org.asyou.mongo.dao.MongoAdapter;
import org.asyou.mongo.dao.MongoHost;
import org.asyou.mongo.query.IQuery;
import org.asyou.mongo.query.QueryFactory;
import org.asyou.mongo.query.QueryUtil;
import org.asyou.mongo.wrapper.DateFromTo;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.tools.data.text.ToolJson;
import pro.tools.data.text.ToolStr;
import pro.tools.format.ToolFormat;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * mongo的工具类
 *
 * @author 陈李 Create By 2017-05-02 10:35
 */
@SuppressWarnings("unchecked")
public class ToolMongo {

    private static final Logger LOG = LoggerFactory.getLogger(cc.tuhaolicai.mt.predefined.tool.ToolMongo.class);

    public static <T> T insertOne(T data) {
        try {
            if (Factory.getAdapter().insertOne(data) == 0L) {
                return data;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return null;
    }

    public static <T> List<T> insertMany(List<T> dataList) {
        try {
            if (Factory.getAdapter().insertMany(dataList) == 0L) {
                return dataList;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return null;
    }

    public static <T> boolean deleteOne(T data) {
        try {
            Factory.getAdapter().deleteOne(data);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private static <T> boolean deleteMany(T data) {
        try {
            Factory.getAdapter().deleteMany(data);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public static <T> boolean updateOne(T queue, T data) {
        try {
            Update.Result result = Factory.getAdapter().updateOne(queue, data);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public static <T> boolean updateMany(T queue, T data) {
        try {
            Update.Result result = Factory.getAdapter().updateMany(queue, data);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    //region find

    //region common
    public static <T> T findOne(T t) {
        return Factory.getAdapter().findOne(t);
    }

    public static <T> List<T> findMany(T t) {
        return findMany(t, new PageInfo(), false, false);
    }

    public static <T> List<T> findMany(T t, PageInfo pageInfo) {
        return findMany(t, pageInfo, false, false);
    }

    public static <T> List<T> findMany(T t, boolean contain, boolean or) {
        return findMany(t, new PageInfo(), contain, or);
    }

    public static <T> List<T> findMany(T t, PageInfo pageInfo, boolean contain, boolean or) {
        return findPage(t, pageInfo, contain, or).getList();
    }
    //endregion

    //region not
    public static <T> List<T> findMany(T t, boolean not) {
        return findMany(t, new PageInfo(), false, false, not);
    }

    public static <T> List<T> findMany(T t, PageInfo pageInfo, boolean not) {
        return findMany(t, pageInfo, false, false, not);
    }

    public static <T> List<T> findMany(T t, boolean contain, boolean or, boolean not) {
        return findMany(t, new PageInfo(), contain, or, not);
    }

    public static <T> List<T> findMany(T t, PageInfo pageInfo, boolean contain, boolean or, boolean not) {
        return findPage(t, pageInfo, contain, or, not).getList();
    }
    //endregion

    //region common
    public static <T> Page<T> findPage(T t) {
        return findPage(t, new PageInfo(), false, false);
    }

    public static <T> Page<T> findPage(T t, PageInfo pageInfo) {
        return findPage(t, pageInfo, false, false);
    }

    public static <T> Page<T> findPage(T t, PageInfo pageInfo, boolean contain, boolean or) {
        pageInfo = ToolPageInfo.valid(pageInfo);

        Map<String, Integer> sortMap = pageInfo.getSortMap();
        String sortStr = ToolJson.mapToJson(sortMap);

        FindMany findMany = find(t, pageInfo.getDateFromTo(), contain, or);
        if (!ToolStr.isBlank(sortStr)) findMany = findMany.sort(sortStr);

        return findMany.page(pageInfo.getPageIndex(), pageInfo.getPageSize());
    }
    //endregion

    //region not
    public static <T> Page<T> findPage(T t, boolean not) {
        return findPage(t, new PageInfo(), false, false, not);
    }

    public static <T> Page<T> findPage(T t, PageInfo pageInfo, boolean not) {
        return findPage(t, pageInfo, false, false, not);
    }

    public static <T> Page<T> findPage(T t, PageInfo pageInfo, boolean contain, boolean or, boolean not) {
        pageInfo = ToolPageInfo.valid(pageInfo);

        Map<String, Integer> sortMap = pageInfo.getSortMap();
        String sortStr = ToolJson.mapToJson(sortMap);

        FindMany findMany = find(t, pageInfo.getDateFromTo(), contain, or, not);
        if (!ToolStr.isBlank(sortStr)) findMany = findMany.sort(sortStr);

        return findMany.page(pageInfo.getPageIndex(), pageInfo.getPageSize());
    }
    //endregion

    //region common
    public static <T> long count(T t) {
        return count(t, new DateFromTo());
    }

    public static <T> long count(T t, DateFromTo dateFromTo) {
        return count(t, dateFromTo, false, false);
    }

    public static <T> long count(T t, boolean contain, boolean or) {
        return count(t, new DateFromTo(), contain, or);
    }

    public static <T> long count(T t, DateFromTo dateFromTo, boolean contain, boolean or) {
        return count(t, dateFromTo, contain, or, false);
    }
    //endregion

    //region not
    public static <T> long count(T t, boolean not) {
        return count(t, new DateFromTo(), not);
    }

    public static <T> long count(T t, DateFromTo dateFromTo, boolean not) {
        return count(t, dateFromTo, false, false, not);
    }
    //endregion

    //region 基础方法
    public static <T> long count(T t, boolean contain, boolean or, boolean not) {
        return count(t, new DateFromTo(), contain, or, not);
    }

    public static <T> long count(T t, DateFromTo dateFromTo, boolean contain, boolean or, boolean not) {
        Count count = Factory.getAdapter().count(t);
        if (dateFromTo != null) count = count.dateFromTo(dateFromTo);
        if (contain) count = count.contain();
        if (or) count = count.OR();
        if (not) count = count.NOT();
        return count.count();
    }

    public static <T> FindMany find(T t, DateFromTo dateFromTo, boolean contain, boolean or) {
        return find(t, dateFromTo, contain, or, false);
    }

    public static <T> FindMany find(T t, DateFromTo dateFromTo, boolean contain, boolean or, boolean not) {
        FindMany findMany = Factory.getAdapter().findMany(t);
        if (dateFromTo != null) findMany = findMany.dateFromTo(dateFromTo);
        if (contain) findMany = findMany.contain();
        if (or) findMany = findMany.OR();
        if (not) findMany = findMany.NOT();
        return findMany;
    }

    public static <T> Paging<T> findAny(PageInfo pageInfo, Class<T> clazz, SearchParam... searchParam) {
        pageInfo = ToolPageInfo.valid(pageInfo);

        List<SearchParam> searchParamList = Lists.newArrayList(searchParam);
        StringBuilder iQuery = new StringBuilder("{");
        searchParamList.forEach(one -> {
            String listAttrStr = list2bson(one.getValues());
            String oneIQuery = "'" + one.getFieldName() + "':{'$in':" + listAttrStr + "}";
            iQuery.append(oneIQuery).append(",");
        });
        iQuery.deleteCharAt(iQuery.length() - 1);
        iQuery.append("}");

        IMongoAdapter adapter = Factory.getAdapter();

        MongoCollection<Document> collection;
        try {
            collection = new MongoHost(adapter, MongoManager.getMongoConfig(adapter.getId()))
                    .getDatabase()
                    .getCollection(
                            QueryUtil.getCollectionName(clazz, null)
                    );
        } catch (Exception e) {
            LOG.warn(ToolFormat.toException(e), e);
            return null;
        }

        IQuery query = new QueryFactory().createQuery(iQuery.toString());

        long totalCount = collection.count(query.toDocument());

        FindIterable<Document> findIterable = collection.find(query.toDocument());

        //分页
        findIterable.skip(pageInfo.getPageIndex() * pageInfo.getPageSize())
                .limit(pageInfo.getPageSize());

        List<T> list = Lists.newLinkedList();

        for (Document document : findIterable) {
            list.add(ToolJson.mapToModel(document, clazz));
        }

        return new Paging<>(pageInfo.getPageIndex(), pageInfo.getPageSize(), totalCount, list);
    }

    public static Long countAny(Class clazz, SearchParam... searchParam) {
        List<SearchParam> searchParamList = Lists.newArrayList(searchParam);
        StringBuilder iQuery = new StringBuilder("{");
        searchParamList.forEach(one -> {
            String listAttrStr = list2bson(one.getValues());
            String oneIQuery = "'" + one.getFieldName() + "':{'$in':" + listAttrStr + "}";
            iQuery.append(oneIQuery).append(",");
        });
        iQuery.deleteCharAt(iQuery.length() - 1);
        iQuery.append("}");

        IMongoAdapter adapter = Factory.getAdapter();

        MongoCollection<Document> collection;
        try {
            collection = new MongoHost(adapter, MongoManager.getMongoConfig(adapter.getId()))
                    .getDatabase()
                    .getCollection(
                            QueryUtil.getCollectionName(clazz, null)
                    );
        } catch (Exception e) {
            LOG.warn(ToolFormat.toException(e), e);
            return null;
        }

        IQuery query = new QueryFactory().createQuery(iQuery.toString());

        return collection.count(query.toDocument());
    }

    public static String list2bson(final List objects) {
        return Tool.list2str("[", "]", ",", objects);
    }
    //endregion

    //endregion

    private static final class Tool {

        public static String list2bson(final List objects) {
            return list2str("[", "]", ",", objects);
        }

        public static String list2str(final String start, final String end, final String sep, final List objects) {
            StringBuilder stringBuilder = new StringBuilder(start);

            objects.forEach(object -> {
                if (object instanceof String) {
                    stringBuilder.append("\"").append(object.toString()).append("\"").append(sep);
                } else {
                    stringBuilder.append(object).append(sep);
                }
            });
            int i = stringBuilder.lastIndexOf(sep);
            return stringBuilder.substring(0, i) + end;
        }
    }

    public static class Factory {

        private static IMongoAdapter instance;

        public static IMongoAdapter getAdapter() {
            if (instance == null) {
                try {
                    instance = new MongoAdapter("test");
                } catch (Exception e) {
                    String message = "初始化Mongo插件失败！";
                    LOG.warn(message);
                    throw new RuntimeException(message, e);
                }
            }
            return instance;
        }
    }

    private static Map<String, Object> jsonObjectToMap(String clzName, JsonObject jsonObject) {
        HashMap<String, Object> hashMap = Maps.newHashMap();
        Set<Map.Entry<String, JsonElement>> entries = jsonObject.entrySet();
        for (Map.Entry<String, JsonElement> entry : entries) {
            JsonElement value = entry.getValue();
            String clzName1 = clzName + "." + entry.getKey();
            if (!(value instanceof JsonObject)) {
                hashMap.put(clzName1, value.getAsString().replaceAll("\"\\w+\"", ""));
            } else {
                JsonObject jsonObject1 = (JsonObject) value;
                hashMap.putAll(jsonObjectToMap(clzName1, jsonObject1));
            }
        }
        return hashMap;
    }
}
